﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace com.susi.util.toolkit {
    public class OverridableDictionary<T, K> : IDictionary<T, K> {
        protected struct Inf {
            public Func<T, K>   Getter;
            public Action<T, K> Setter;
            public K            Val;

            public K GetVal(T key)
            {
                return Getter == null ? Val : Getter(key);
            }

            public void SetVal(T key, K val)
            {
                if (Setter != null) {
                    Setter(key, val);
                }
                else Val = val;
            }
        }

        protected Dictionary<T, Inf> mDic = new Dictionary<T, Inf>();

        public virtual K this[T key] {
            get {
                if (mDic.TryGetValue(key, out var inf)) {
                    inf.GetVal(key);
                }

                return default;
            }
            set {
                if (mDic.TryGetValue(key, out var inf)) {
                    inf.SetVal(key, value);
                }

                throw new ArgumentException("NoSuchKey");
            }
        }

        public virtual ICollection<T> Keys => mDic.Keys;

        public virtual ICollection<K> Values {
            get {
                var v = from item in mDic
                        let v1 = item.Value.GetVal(item.Key)
                        select v1;
                return v.ToArray();
            }
        }

        public virtual int Count => mDic.Count;

        public virtual bool IsReadOnly => false;

        public virtual void Add(T key, K value)
        {
            if (mDic.ContainsKey(key)) {
                throw new Exception("Key has already exist");
            }

            mDic.Add(key, new Inf() {Val = value});
        }


        public virtual void Add(KeyValuePair<T, K> item)
        {
            Add(item.Key, item.Value);
        }

        public virtual void Add(T key, Func<T, K> getter, Action<T, K> setter)
        {
            if (mDic.ContainsKey(key)) {
                throw new Exception("Key has already exist");
            }

            mDic.Add(key, new Inf() {Getter = getter, Setter = setter});
        }

        public virtual void Clear()
        {
            throw new NotSupportedException();
        }

        public virtual bool Contains(KeyValuePair<T, K> item)
        {
            throw new NotSupportedException();
        }

        public virtual bool ContainsKey(T key)
        {
            return mDic.ContainsKey(key);
        }

        public virtual void CopyTo(KeyValuePair<T, K>[] array, int arrayIndex)
        {
            throw new NotSupportedException();
        }

        public virtual IEnumerator<KeyValuePair<T, K>> GetEnumerator()
        {
            return new ODEnumerator(mDic.GetEnumerator());
        }

        public virtual bool Remove(T key)
        {
            throw new NotSupportedException();
        }

        public virtual bool Remove(KeyValuePair<T, K> item)
        {
            throw new NotSupportedException();
        }

        public virtual bool TryGetValue(T key, out K value)
        {
            if (mDic.TryGetValue(key, out var gs)) {
                value = gs.GetVal(key);
                return true;
            }

            value = default;
            return false;
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }


        private struct ODEnumerator : IEnumerator<KeyValuePair<T, K>>, IEnumerator, IDisposable, IDictionaryEnumerator {
            IEnumerator<KeyValuePair<T, Inf>> Enumerator;

            public ODEnumerator(IEnumerator<KeyValuePair<T, Inf>> enumerator)
            {
                Enumerator = enumerator;
            }

            public KeyValuePair<T, K> Current => new KeyValuePair<T, K>(Enumerator.Current.Key, Enumerator.Current.Value.GetVal(Enumerator.Current.Key));

            public DictionaryEntry Entry => new DictionaryEntry(Enumerator.Current.Key, Enumerator.Current.Value.GetVal(Enumerator.Current.Key));

            public object Key => Enumerator.Current.Key;

            public object Value => Enumerator.Current.Value.GetVal(Enumerator.Current.Key);

            object IEnumerator.Current => Current;

            public void Dispose()
            {
                Enumerator.Dispose();
            }

            public bool MoveNext()
            {
                return Enumerator.MoveNext();
            }

            public void Reset()
            {
                Enumerator.Reset();
            }
        }
    }
}